package com.hucsmn.examsystem.client.view;

import com.hucsmn.examsystem.client.ExamClient;
import com.hucsmn.examsystem.client.ExamClientException;
import com.hucsmn.examsystem.common.AnswerSheet;
import com.hucsmn.examsystem.common.Problem;
import com.hucsmn.examsystem.common.User;

import java.io.IOException;
import java.net.InetAddress;
import java.util.HashSet;
import java.util.Objects;
import java.util.Scanner;
import java.util.Set;
import java.util.function.IntPredicate;
import java.util.regex.Pattern;

/**
 * 在线考试系统客户端界面。
 */
public class ExamClientView {
    // 工具方法使用的正则。

    private static final Pattern REGEX_SPACE = Pattern.compile("\\s+");

    // 全局状态。

    private final String prompt = "> ";
    private final ExamClient client;
    private final Scanner scanner;

    public ExamClientView() {
        this(ExamClient.ADDRESS, ExamClient.PORT, ExamClient.TIMEOUT);
    }

    public ExamClientView(int port) {
        this(ExamClient.ADDRESS, port, ExamClient.TIMEOUT);
    }

    public ExamClientView(InetAddress address, int port) {
        this(address, port, ExamClient.TIMEOUT);
    }

    public ExamClientView(InetAddress address, int port, int timeout) {
        client = new ExamClient(address, port, timeout);
        scanner = new Scanner(System.in);
    }

    int wizard(String title, String quit, String... items) {
        System.out.println("=".repeat(80));
        if (title != null) {
            System.out.println(title);
            System.out.println("-".repeat(80));
        }
        for (int i = 0; i < items.length; i++) {
            System.out.println(String.format("  [%d] %s", i + 1, items[i]));
        }
        if (quit != null) {
            System.out.println("  [0] " + quit);
        }
        System.out.println("-".repeat(80));

        if (quit != null) {
            return promptInt(n -> n >= 0 && n <= items.length);
        } else {
            return promptInt(n -> n > 0 && n <= items.length);
        }
    }

    int promptInt() {
        return promptInt(prompt);
    }

    int promptInt(IntPredicate predicate) {
        return promptInt(prompt, predicate);
    }

    // 构造函数。

    int promptInt(String prompt) {
        return promptInt(prompt, n -> true);
    }

    int promptInt(String prompt, IntPredicate predicate) {
        while (true) {
            System.out.print(prompt);
            String literal = scanner.nextLine();

            try {
                int n = Integer.parseInt(literal);
                if (predicate.test(n)) {
                    return n;
                } else {
                    System.out.println("输入的数值范围不正确，请重新输入！");
                }
            } catch (NumberFormatException e) {
                System.out.println("输入的数值格式不正确，请重新输入！");
            }
        }
    }

    String promptString() {
        return promptString(prompt);
    }

    String promptString(String prompt) {
        System.out.print(prompt);
        return scanner.nextLine();
    }

    // 工具方法。

    String[] promptStringArray() {
        return promptStringArray(prompt);
    }

    String[] promptStringArray(String prompt) {
        System.out.print(prompt);
        return REGEX_SPACE.split(scanner.nextLine().trim());
    }

    String getExceptionMessage(Exception e) {
        if (e instanceof ExamClientException) {
            return e.getMessage();
        } else {
            return Objects.toString(e);
        }
    }

    /**
     * 主界面。
     */
    public class MainView implements Runnable {
        @Override
        public void run() {
            while (true) {
                int n = wizard("在线考试系统", "退出",
                        "学生系统", "管理员系统");
                switch (n) {
                    case 1:
                        new StudentView().run();
                        break;
                    case 2:
                        new AdminView().run();
                        break;
                    default:
                        return;
                }
                System.out.println();
            }
        }
    }

    /**
     * 学生系统界面。
     */
    public class StudentView implements Runnable {
        @Override
        public void run() {
            while (true) {
                int n = wizard("学生系统", "退出",
                        "登录", "登出", "修改密码", "开始考试", "查询成绩");
                switch (n) {
                    case 1:
                        studentLogin();
                        break;
                    case 2:
                        studentLogout();
                        break;
                    case 3:
                        studentChangePassword();
                        break;
                    case 4:
                        new StudentExamView().run();
                        break;
                    case 5:
                        new StudentAnswerSheetsView().run();
                        break;
                    default:
                        return;
                }
            }
        }

        private void studentLogin() {
            while (true) {
                String name = promptString("学员用户名：");
                String pass = promptString("学员密码：");
                User user = new User(0, name, pass);
                try {
                    client.login(user, false);
                    System.out.println("登陆成功！");
                    return;
                } catch (IOException e) {
                    System.out.println("登录错误：" + getExceptionMessage(e));
                }
            }
        }

        private void studentLogout() {
            try {
                client.logout();
            } catch (IOException e) {
                System.out.println("登出错误：" + getExceptionMessage(e));
            }
        }

        private void studentChangePassword() {
            User loginUser = client.getLoginUser();
            if (loginUser == null) {
                System.out.println("尚未登录！");
                return;
            }

            while (true) {
                String pass = promptString("新的密码：");
                String repeat = promptString("重复密码：");
                if (!repeat.equals(pass)) {
                    System.out.println("两遍密码不相同！");
                    continue;
                }

                User user = new User(0, loginUser.getName(), pass);
                try {
                    client.updateSelf(user);
                    System.out.println("密码修改成功！");
                    return;
                } catch (IOException e) {
                    System.out.println("密码修改错误：" + getExceptionMessage(e));
                }
            }
        }
    }

    /**
     * 学生考试界面。
     */
    public class StudentExamView implements Runnable {
        private final Pattern REGEX_CHOICE = Pattern.compile("^[A-Z]$");

        @Override
        public void run() {
            try {
                AnswerSheet answerSheet = client.beginExam();
                System.out.println(answerSheet);

                Problem[] problems = answerSheet.getProblems();
                for (int i = 0; i < answerSheet.length(); i++) {
                    input:
                    while (true) {
                        String[] choices = promptStringArray("输入第 " + (i + 1) + " 题的答案：");
                        if (choices.length == 1 && "放弃".equals(choices[0])) {
                            System.out.println("放弃答题...");
                            client.endExam();
                            return;
                        }

                        if (choices.length == 0 || choices.length > (problems[i].isMultiple() ? problems[i].length() : 1)) {
                            System.out.println("选项数量不正确！");
                            continue;
                        }

                        Set<Integer> toggled = new HashSet<>();
                        for (String choice : choices) {
                            if (choice.length() != 1 || choice.charAt(0) < 'A' || choice.charAt(0) > 'Z') {
                                System.out.println("选项标号不正确！");
                                continue input;
                            }
                            int n = choice.charAt(0) - 'A';
                            if (n < 0 || n >= problems[i].length()) {
                                System.out.println("选项标号超出范围！");
                                continue input;
                            }
                            toggled.add(n);
                        }
                        problems[i].setToggled(toggled.stream().mapToInt(x -> x).toArray());
                        break;
                    }
                }

                System.out.println("答题结束上传考卷...");
                AnswerSheet corrected = client.endExam(answerSheet);

                System.out.println("批改后的试卷：");
                System.out.println(corrected.detailedToString());
            } catch (IOException e) {
                System.out.println("考试失败：" + getExceptionMessage(e));
            }
        }
    }

    /**
     * 学生查询成绩界面。
     */
    public class StudentAnswerSheetsView implements Runnable {
        @Override
        public void run() {
            while (true) {
                int n = wizard("学生成绩查询", "退出",
                        "查询上次考试", "查询全部试卷");
                switch (n) {
                    case 1:
                        queryRecentExam();
                        break;
                    case 2:
                        queryAllExams();
                        break;
                    default:
                        return;
                }
            }
        }

        private void queryRecentExam() {
            try {
                AnswerSheet answerSheet = client.recentExam();
                System.out.println(answerSheet);
            } catch (IOException e) {
                System.out.println("查询最近考试成绩错误：" + getExceptionMessage(e));
            }
        }

        private void queryAllExams() {
            try {
                for (AnswerSheet answerSheet : client.listExams()) {
                    System.out.println(answerSheet.detailedToString());
                    System.out.println();
                }
            } catch (IOException e) {
                System.out.println("查询考试成绩错误：" + getExceptionMessage(e));
            }
        }
    }

    /**
     * 管理员界面。
     */
    public class AdminView implements Runnable {
        @Override
        public void run() {
            adminLogin();

            while (true) {
                int n = wizard("管理员系统", "退出",
                        "学员管理", "考题管理");
                switch (n) {
                    case 1:
                        new UserManageView().run();
                        break;
                    case 2:
                        new ProblemManageView().run();
                        break;
                    default:
                        adminLogout();
                        return;
                }
            }
        }

        private void adminLogin() {
            while (true) {
                String name = promptString("管理员用户名：");
                String pass = promptString("管理员密码：");
                User user = new User(0, name, pass);
                try {
                    client.login(user, true);
                    System.out.println("登陆成功！");
                    return;
                } catch (IOException e) {
                    System.out.println("登录错误：" + getExceptionMessage(e));
                }
            }
        }

        private void adminLogout() {
            try {
                client.logout();
            } catch (IOException e) {
                System.out.println("登出错误：" + getExceptionMessage(e));
            }
        }
    }

    /**
     * 用户管理界面。
     */
    public class UserManageView implements Runnable {
        @Override
        public void run() {
            while (true) {
                int n = wizard("学员管理", "退出",
                        "增加学员", "删除学员", "修改学员", "查询学员", "列举学员");
                switch (n) {
                    case 1:
                        addUser();
                        break;
                    case 2:
                        deleteUser();
                        break;
                    case 3:
                        updateUser();
                        break;
                    case 4:
                        getUser();
                        break;
                    case 5:
                        listUsers();
                        break;
                    default:
                        return;
                }
            }
        }

        private void addUser() {
            try {
                String name = promptString("学员名称：");
                String pass = promptString("学员密码：");
                User user = new User(0, name, pass);
                int uid = client.addUser(user);
                System.out.println("增加学员成功，编号：" + uid);
            } catch (IOException e) {
                System.out.println("增加学员错误：" + getExceptionMessage(e));
            }
        }

        private void deleteUser() {
            try {
                int uid = promptInt("学员编号：");
                client.deleteUser(uid);
                System.out.println("删除学员成功");
            } catch (IOException e) {
                System.out.println("删除学员错误：" + getExceptionMessage(e));
            }
        }

        private void updateUser() {
            try {
                int uid = promptInt("学员编号：");
                String name = promptString("学员名称：");
                String pass = promptString("学员密码：");
                User user = new User(uid, name, pass);
                client.updateUser(user);
                System.out.println("修改学员成功");
            } catch (IOException e) {
                System.out.println("修改学员错误：" + getExceptionMessage(e));
            }
        }

        private void getUser() {
            try {
                int uid = promptInt("学员编号：");
                User user = client.getUser(uid);
                System.out.println("查询学员成功：" + user);
            } catch (IOException e) {
                System.out.println("查询学员错误：" + getExceptionMessage(e));
            }
        }

        private void listUsers() {
            try {
                System.out.println("列举学员...");
                for (User user : client.listUsers()) {
                    System.out.println(user);
                }
            } catch (IOException e) {
                System.out.println("查询学员错误：" + getExceptionMessage(e));
            }
        }
    }

    /**
     * 题库管理界面。
     */
    public class ProblemManageView implements Runnable {
        @Override
        public void run() {
            while (true) {
                int n = wizard("考题管理", "退出",
                        "增加考题", "删除考题", "修改考题", "查询考题", "列举考题");
                switch (n) {
                    case 1:
                        addProblem();
                        break;
                    case 2:
                        deleteProblem();
                        break;
                    case 3:
                        updateProblem();
                        break;
                    case 4:
                        getProblem();
                        break;
                    case 5:
                        listProblems();
                        break;
                    default:
                        return;
                }
            }
        }

        private void addProblem() {
            try {
                Problem problem = inputProblem();
                int pid = client.addProblem(problem);
                System.out.println("增加题目成功，编号：" + pid);
            } catch (IOException e) {
                System.out.println("增加题目错误：" + getExceptionMessage(e));
            }
        }

        private void deleteProblem() {
            try {
                int pid = promptInt("题目编号：");
                client.deleteProblem(pid);
                System.out.println("删除题目成功");
            } catch (IOException e) {
                System.out.println("删除题目错误：" + getExceptionMessage(e));
            }
        }

        private void updateProblem() {
            try {
                int pid = promptInt("题目编号：");
                Problem problem = inputProblem();
                problem.setId(pid);

                client.updateProblem(problem);
                System.out.println("修改题目成功");
            } catch (IOException e) {
                System.out.println("修改题目错误：" + getExceptionMessage(e));
            }
        }

        private void getProblem() {
            try {
                int pid = promptInt("题目编号：");
                Problem problem = client.getProblem(pid);
                System.out.println("查询题目成功：");
                System.out.println(problem.detailedToString());
            } catch (IOException e) {
                System.out.println("查询题目错误：" + getExceptionMessage(e));
            }
        }

        private void listProblems() {
            try {
                System.out.println("列举题目...");
                for (Problem problem : client.listProblems()) {
                    System.out.println(problem.detailedToString());
                }
            } catch (IOException e) {
                System.out.println("列举题目错误：" + getExceptionMessage(e));
            }
        }

        private Problem inputProblem() {
            String problem = promptString("输入题面：");

            int count = promptInt("输入选项数：", n -> n >= Problem.MIN_CHOICES && n <= Problem.MAX_CHOICES);
            String[] choices = new String[count];
            for (int i = 0; i < count; i++) {
                String choice = promptString("输入第 " + (i + 1) + " 个选项：");
                choices[i] = choice;
            }

            boolean multiple = false;
            String multipleString = promptString("是否为多选题：");
            switch (multipleString) {
                case "y":
                case "是":
                case "多选":
                    multiple = true;
            }

            Problem result = new Problem(0, problem, 10, multiple, choices);

            input:
            while (true) {
                String[] answerStrings = promptStringArray("输入正确答案：");
                if (answerStrings.length == 0 || answerStrings.length > (multiple ? count : 1)) {
                    System.out.println("选项数量不正确！");
                    continue;
                }

                Set<Integer> answers = new HashSet<>();
                for (String answerString : answerStrings) {
                    if (answerString.length() != 1 || answerString.charAt(0) < 'A' || answerString.charAt(0) > 'Z') {
                        System.out.println("选项标号不正确！");
                        continue input;
                    }
                    int n = answerString.charAt(0) - 'A';
                    if (n < 0 || n >= count) {
                        System.out.println("选项标号超出范围！");
                        continue input;
                    }
                    answers.add(n);
                }
                result.setAnswers(answers.stream().mapToInt(x -> x).toArray());
                break;
            }

            return result;
        }
    }
}
